package net.greyar.gear;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import net.greyar.model.Server;
import net.greyar.single.Register;
import net.greyar.theory.Literal;
import net.greyar.theory.State;
import net.greyar.theory.Substitution;
import net.greyar.theory.Symbol;
import net.greyar.theory.Variable;
import net.greyar.theory.View;
public class Machine {
	private long tick = 1;
	private boolean initialized;
	private boolean locked;
	private Server server;
	private Symbol name;
	private Symbol game;
	private List<Symbol> rules;
	private List<Symbol> log;
	private State state;
	public Machine(Server server, String game, List<Symbol> rules) throws IOException {
		this.server = server;
		this.name = new Literal(Register.name(this));
		this.game = new Literal(game);
		this.rules = Collections.unmodifiableList(rules);
		this.log = new ArrayList<Symbol>();
		this.state = new State(this.rules);
		this.locked = false;
		this.initialized = false;
		log.add(Symbol.parse("(log " + tick + " (online))"));
		locked = true;
		update();
		locked = false;
	}
	private void init() {
		List<Symbol> facts = state.results(Symbol.parse("init ?x"));
		for (Symbol fact : facts) {
			state.addFact(fact.get(1));
		}
		initialized = true;
		log.add(Symbol.parse("(log " + tick + " (init))"));
	}
	public void move(Symbol user, Symbol action) throws IOException {
		if (!locked) {
			locked = true;
			Symbol move = Symbol.parse("move " + user + " " + action);
			state.addFact(move);
			log.add(Symbol.parse("(log " + tick + " (" + move + "))"));
			update();
			locked = true;
		}
	}
	public void input(Symbol data) throws IOException {
		if (!locked) {
			locked = true;
			Symbol input = Symbol.parse("(input " + data + ")");
			state.addFact(input);
			log.add(Symbol.parse("(log " + tick + " (" + input + "))"));
			update();
			locked = true;
		}
	}
	private void next() {
		tick++;
		List<Symbol> next;
		boolean shuffle = !state.results(Symbol.parse("shuffle")).isEmpty();
		if (shuffle) {
			next = shuffle();
		} else {
			next = state.results(Symbol.parse("next ?x"));
		}
		state.clear();
		log.add(Symbol.parse("(log " + tick + " (" + (shuffle ? "shuffle" : "next") + "))"));
		for (Symbol fact : next) {
			log.add(Symbol.parse("(log " + tick + " (" + (shuffle ? "shuffle" : "next") + ": added " + fact.get(1) + "))"));
			state.addFact(fact.get(1));
		}
	}
	private List<Symbol> shuffle() {
		List<Symbol> shuffle = state.results(Symbol.parse("shuffle ?x"));
		List<Symbol> next = new ArrayList<Symbol>();
		for (Symbol target : shuffle) {
			List<Substitution> data = state.find(Symbol.parse("next " + target.get(1)));
			List<Substitution> results = new ArrayList<Substitution>();
			for (int i = 0; i < data.size(); i++) {
				results.add(new Substitution());
			}
			for (Variable variable : target.getVariables()) {
				List<Substitution> shuffled = new ArrayList<Substitution>(data);
				Collections.shuffle(shuffled);
				for (int i = 0; i < shuffled.size(); i++) {
					results.get(i).bind(variable, shuffled.get(i).last(variable));
				}
			}
			for (Substitution sigma : results) {
				next.add(target.apply(sigma));
			}
		}
		next.addAll(state.results(Symbol.parse("rest ?x")));
		return next;
	}
	public Symbol getName() {
		return name;
	}
	public Symbol getGame() {
		return game;
	}
	public List<Symbol> getRules() {
		return rules;
	}
	/**
	 * @return
	 * @throws Exception 
	 */
	private boolean doInit() {
		return !state.find(Symbol.parse("init")).isEmpty();
	}
	/**
	 * @return
	 * @throws Exception 
	 */
	public boolean doServe() {
		return !state.find(Symbol.parse("serve")).isEmpty();
	}
	/**
	 * @return
	 * @throws Exception 
	 */
	public boolean doNext() {
		return !state.find(Symbol.parse("next")).isEmpty();
	}
	/**
	 * @throws IOException 
	 * @throws Exception 
	 * 
	 */
	private void update() throws IOException {
		if (!initialized) {
			if (doInit()) {
				init();
			}
		}
		while (doServe() || doNext()) {
			while (doServe()) {
				List<Symbol> tasks = state.results(Symbol.parse("serve ?x"));
				for (Symbol task : tasks) {
					Symbol input = server.execute(task.get(1));
					state.addFact(Symbol.parse("input " + input));
					state.addFact(Symbol.parse("served " + task.get(1)));
					log.add(Symbol.parse("(log " + tick + " (server : added " + Symbol.parse("input " + input) + "))"));
					log.add(Symbol.parse("(log " + tick + " (server : added " + Symbol.parse("served " + task.get(1)) + "))"));
				}
			}
			if (doNext()) {
				next();
			}
		}
	}
	public View view(Symbol user) {
		View view = new View(user, name, game, rules, log);
		view.addFacts(state.results(Symbol.parse("sees " + user + " ?data")));
		view.addFacts(state.results(Symbol.parse("legal " + user + " ?action")));
		return view;
	}
}
